using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.Measurement;
using ZoneFiveSoftware.Common.Visuals;
using FilteredStatistics.Common;
using FilteredStatistics.Controller;
using FilteredStatistics.Data;
using FilteredStatistics.View;

namespace FilteredStatistics.Common.Controller
{
    class FilteredStatisticsStatisticsProvider : IStatisticsProvider
    {
        public FilteredStatisticsStatisticsProvider()
        {
        }

        public static FilteredStatisticsStatisticsProvider Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    m_Instance = new FilteredStatisticsStatisticsProvider();
                }

                return m_Instance;
            }
        }

#region IStatisticsProvider members

        public IList<Guid> ColumnIds
        {
            get
            {
                List<Guid> columns = new List<Guid>();

                columns.Add(new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"));
                columns.Add(new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"));
                columns.Add(new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"));
                columns.Add(new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"));
                columns.Add(new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"));
                columns.Add(new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"));
                columns.Add(new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"));
                columns.Add(new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"));
                columns.Add(new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"));
                columns.Add(new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"));
                columns.Add(new Guid("1b06055e-c232-4383-9d43-50e558a69af1"));
                columns.Add(new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"));
                columns.Add(new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"));
                columns.Add(new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"));
                columns.Add(new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"));
                columns.Add(new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"));
                columns.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"));
                columns.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"));
                columns.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"));
                columns.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"));
                columns.Add(new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"));
                columns.Add(new Guid("72a37849-3d55-457b-8cc1-a87206104261"));
                columns.Add(new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"));
                columns.Add(new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"));
                columns.Add(new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"));
                columns.Add(new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"));
                columns.Add(new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"));
                columns.Add(new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"));
                columns.Add(new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"));
                columns.Add(new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"));
                columns.Add(new Guid("550e262d-2563-4131-9f37-04ceda1a8097"));
                columns.Add(new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"));
                columns.Add(new Guid("76695911-807c-44cc-ac7a-3367184db00e"));
                columns.Add(new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"));
                columns.Add(new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"));
                columns.Add(new Guid("3210bbff-605b-4978-abca-4f336a2268b0"));
                columns.Add(new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"));
                columns.Add(new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"));
                columns.Add(new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"));

                return columns;
            }
        }

        public String GetColumnHeader(Guid columnId, IActivity activity)
        {
            Dictionary<Guid, String> idToHeaderTable = new Dictionary<Guid, String>();

            idToHeaderTable.Add(new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"), CommonResources.Text.LabelStartTime);
            idToHeaderTable.Add(new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"), CommonResources.Text.LabelEndTime);
            idToHeaderTable.Add(new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"), CommonResources.Text.LabelTime);
            idToHeaderTable.Add(new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"), PluginView.GetLocalizedString("StartDistanceText"));
            idToHeaderTable.Add(new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"), PluginView.GetLocalizedString("EndDistanceText"));
            idToHeaderTable.Add(new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"), CommonResources.Text.LabelDistance);
            idToHeaderTable.Add(new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"), PluginView.GetLocalizedString("TimePercentText"));
            idToHeaderTable.Add(new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"), PluginView.GetLocalizedString("DistancePercentText"));
            idToHeaderTable.Add(new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"), PluginView.GetLocalizedString("StartElevationText"));
            idToHeaderTable.Add(new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"), PluginView.GetLocalizedString("EndElevationText"));
            idToHeaderTable.Add(new Guid("1b06055e-c232-4383-9d43-50e558a69af1"), CommonResources.Text.LabelElevationChange);
            idToHeaderTable.Add(new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"), PluginView.GetLocalizedString("TotalAscentText"));
            idToHeaderTable.Add(new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"), PluginView.GetLocalizedString("TotalDescentText"));
            idToHeaderTable.Add(new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"), PluginView.GetLocalizedString("AscentDistanceText"));
            idToHeaderTable.Add(new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"), PluginView.GetLocalizedString("FlatDistanceText"));
            idToHeaderTable.Add(new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"), PluginView.GetLocalizedString("DescentDistanceText"));

            if (activity != null && activity.Category.SpeedUnits == Speed.Units.Pace)
            {
                idToHeaderTable.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"), PluginView.GetLocalizedString("MinPaceText"));
                idToHeaderTable.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"), CommonResources.Text.LabelAvgPace);
                idToHeaderTable.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"), PluginView.GetLocalizedString("MaxPaceText"));
                idToHeaderTable.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"), CommonResources.Text.LabelPaceVsAverage);
            }
            else
            {
                idToHeaderTable.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"), PluginView.GetLocalizedString("MinSpeedText"));
                idToHeaderTable.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"), CommonResources.Text.LabelAvgSpeed);
                idToHeaderTable.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"), PluginView.GetLocalizedString("MaxSpeedText"));
                idToHeaderTable.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"), CommonResources.Text.LabelSpeedVsAverage);
            }

            String temp = PluginView.GetLocalizedString("VerticalSpeedText");
            if (activity != null)
            {
                temp += "(" + Length.LabelAbbr(activity.Category.ElevationUnits) + "/" + CommonResources.Text.LabelHour_lower + ")";
            }
            idToHeaderTable.Add(new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"), temp);

            idToHeaderTable.Add(new Guid("72a37849-3d55-457b-8cc1-a87206104261"), PluginView.GetLocalizedString("MinCadenceText"));
            idToHeaderTable.Add(new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"), CommonResources.Text.LabelAvgCadence);
            idToHeaderTable.Add(new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"), CommonResources.Text.LabelMaxCadence);
            idToHeaderTable.Add(new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"), PluginView.GetLocalizedString("CadenceVsAvgText"));
            idToHeaderTable.Add(new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"), PluginView.GetLocalizedString("MinHRText"));
            idToHeaderTable.Add(new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"), CommonResources.Text.LabelAvgHR);
            idToHeaderTable.Add(new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"), CommonResources.Text.LabelMaxHR);
            idToHeaderTable.Add(new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"), PluginView.GetLocalizedString("HRVsAvgText"));
            idToHeaderTable.Add(new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"), PluginView.GetLocalizedString("MinPowerText"));
            idToHeaderTable.Add(new Guid("550e262d-2563-4131-9f37-04ceda1a8097"), PluginView.GetLocalizedString("AvgPowerText"));
            idToHeaderTable.Add(new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"), CommonResources.Text.LabelMaxPower);
            idToHeaderTable.Add(new Guid("76695911-807c-44cc-ac7a-3367184db00e"), PluginView.GetLocalizedString("PowerVsAvgText"));
            idToHeaderTable.Add(new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"), PluginView.GetLocalizedString("MinGradeText"));
            idToHeaderTable.Add(new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"), PluginView.GetLocalizedString("AvgGradeText"));
            idToHeaderTable.Add(new Guid("3210bbff-605b-4978-abca-4f336a2268b0"), PluginView.GetLocalizedString("AvgAscentGradeText"));
            idToHeaderTable.Add(new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"), PluginView.GetLocalizedString("AvgDescentGradeText"));
            idToHeaderTable.Add(new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"), PluginView.GetLocalizedString("MaxGradeText"));
            idToHeaderTable.Add(new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"), PluginView.GetLocalizedString("GradeVsAvgText"));

            Debug.Assert(idToHeaderTable.ContainsKey(columnId));

            return idToHeaderTable[columnId];
        }

        public Color GetColumnColor(Guid columnId)
        {
            Dictionary<Guid, Color> idToColorTable = new Dictionary<Guid, Color>();

            idToColorTable.Add(new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"), Constants.LineChartTypesColors[1]);
            idToColorTable.Add(new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"), Constants.LineChartTypesColors[1]);
            idToColorTable.Add(new Guid("1b06055e-c232-4383-9d43-50e558a69af1"), Constants.LineChartTypesColors[1]);
            idToColorTable.Add(new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"), Constants.LineChartTypesColors[1]);
            idToColorTable.Add(new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"), Constants.LineChartTypesColors[1]);
            idToColorTable.Add(new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"), PluginMain.GetApplication().VisualTheme.ControlText);
            idToColorTable.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"), Constants.LineChartTypesColors[5]);
            idToColorTable.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"), Constants.LineChartTypesColors[5]);
            idToColorTable.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"), Constants.LineChartTypesColors[5]);
            idToColorTable.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"), Constants.LineChartTypesColors[5]);
            idToColorTable.Add(new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"), Constants.LineChartTypesColors[5]);
            idToColorTable.Add(new Guid("72a37849-3d55-457b-8cc1-a87206104261"), Constants.LineChartTypesColors[0]);
            idToColorTable.Add(new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"), Constants.LineChartTypesColors[0]);
            idToColorTable.Add(new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"), Constants.LineChartTypesColors[0]);
            idToColorTable.Add(new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"), Constants.LineChartTypesColors[0]);
            idToColorTable.Add(new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"), Constants.LineChartTypesColors[3]);
            idToColorTable.Add(new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"), Constants.LineChartTypesColors[3]);
            idToColorTable.Add(new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"), Constants.LineChartTypesColors[3]);
            idToColorTable.Add(new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"), Constants.LineChartTypesColors[3]);
            idToColorTable.Add(new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"), Constants.LineChartTypesColors[4]);
            idToColorTable.Add(new Guid("550e262d-2563-4131-9f37-04ceda1a8097"), Constants.LineChartTypesColors[4]);
            idToColorTable.Add(new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"), Constants.LineChartTypesColors[4]);
            idToColorTable.Add(new Guid("76695911-807c-44cc-ac7a-3367184db00e"), Constants.LineChartTypesColors[4]);
            idToColorTable.Add(new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"), Constants.LineChartTypesColors[2]);
            idToColorTable.Add(new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"), Constants.LineChartTypesColors[2]);
            idToColorTable.Add(new Guid("3210bbff-605b-4978-abca-4f336a2268b0"), Constants.LineChartTypesColors[2]);
            idToColorTable.Add(new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"), Constants.LineChartTypesColors[2]);
            idToColorTable.Add(new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"), Constants.LineChartTypesColors[2]);
            idToColorTable.Add(new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"), Constants.LineChartTypesColors[2]);

            Debug.Assert(idToColorTable.ContainsKey(columnId));

            return idToColorTable[columnId];
        }

        public String GetStatisticLabel(Guid columnId, object currentRange)
        {
            MethodInfo getLabelMethod = GetType().GetMethod("Get" + GetValueMemberFromGuid(columnId), new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(getLabelMethod != null && getLabelMethod.ReturnType == typeof(String));

            return getLabelMethod.Invoke(this, new object[] { currentRange }) as String;
        }

        public String GetTotalStatisticLabel(Guid columnId, IList<object> allRanges)
        {
            MethodInfo getLabelMethod = GetType().GetMethod("Get" + GetValueMemberFromGuid(columnId), new Type[] { typeof(List<ActivityRangeInfoCache>) });

            Debug.Assert(getLabelMethod != null && getLabelMethod.ReturnType == typeof(String));

            List<ActivityRangeInfoCache> allWrappers = new List<ActivityRangeInfoCache>();

            foreach (object range in allRanges)
            {
                allWrappers.Add(range as ActivityRangeInfoCache);
            }

            return getLabelMethod.Invoke(this, new object[] { allWrappers }) as String;
        }

        public int CompareRanges(Guid columnId, int sortingOrder, object range1, object range2)
        {
            TreeListColumnInfo.ColumnSortingType sortOrder = (TreeListColumnInfo.ColumnSortingType)sortingOrder;
            String sortMember = GetValueMemberFromGuid(columnId);
            MethodInfo sortMemberMethod = GetType().GetMethod("Get" + sortMember, new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(sortMemberMethod != null && sortMemberMethod.ReturnType == typeof(String));

            if (sortMemberMethod.Invoke(this, new object[] { range1 }) as String == String.Empty)
            {
                // Revert to default value, we don't have this info
                sortMember = "StartTime";
                sortOrder = TreeListColumnInfo.ColumnSortingType.Ascending;
            }

            MethodInfo valueMethod = GetType().GetMethod("Get" + sortMember + "Value", new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(valueMethod != null);

            double valueX;
            double valueY;
            double result;

            // Special case for time spans
            if (sortMember == "StartTime" ||
                sortMember == "EndTime" ||
                sortMember == "ElapsedTime")
            {
                Debug.Assert(valueMethod.ReturnType == typeof(TimeSpan));

                TimeSpan tempValueX = (TimeSpan)valueMethod.Invoke(this, new object[] { range1 });
                TimeSpan tempValueY = (TimeSpan)valueMethod.Invoke(this, new object[] { range2 });

                valueX = tempValueX.TotalSeconds;
                valueY = tempValueY.TotalSeconds;
            }
            else
            {
                valueX = (double)valueMethod.Invoke(this, new object[] { range1 });
                valueY = (double)valueMethod.Invoke(this, new object[] { range2 });
            }

            if (sortOrder == TreeListColumnInfo.ColumnSortingType.Ascending)
            {
                result = valueX - valueY;
            }
            else
            {
                result = valueY - valueX;
            }

            return Math.Sign(result);
        }

#endregion

#region Single range stats

        public TimeSpan GetStartTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Lower - range.ActivityInfo.ActualTrackStart;
        }

        public String GetStartTime(ActivityRangeInfoCache range)
        {
            TimeSpan startTime = GetStartTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", startTime.Hours, startTime.Minutes, startTime.Seconds);
        }

        public TimeSpan GetEndTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Upper - range.ActivityInfo.ActualTrackStart;
        }

        public String GetEndTime(ActivityRangeInfoCache range)
        {
            TimeSpan endTime = GetEndTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", endTime.Hours, endTime.Minutes, endTime.Seconds);
        }

        public double GetStartDistanceValue(ActivityRangeInfoCache range)
        {
            return Length.Convert(range.PauselessRangeDistanceTrack.Min, Length.Units.Meter, range.Activity.Category.DistanceUnits);
        }

        public String GetStartDistance(ActivityRangeInfoCache range)
        {
            return String.Format("{0:0.0} {1}", GetStartDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
        }

        public double GetEndDistanceValue(ActivityRangeInfoCache range)
        {
            return Length.Convert(range.PauselessRangeDistanceTrack.Max, Length.Units.Meter, range.Activity.Category.DistanceUnits);
        }

        public String GetEndDistance(ActivityRangeInfoCache range)
        {
            return String.Format("{0:0.0} {1}", GetEndDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
        }

        public TimeSpan GetElapsedTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Upper - range.PauselessRange.Lower;
        }

        public String GetElapsedTime(ActivityRangeInfoCache range)
        {
            TimeSpan elapsedTime = GetElapsedTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", elapsedTime.Hours, elapsedTime.Minutes, elapsedTime.Seconds);
        }

        public double GetDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeDistanceTrack.Max - range.PauselessRangeDistanceTrack.Min, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", GetDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetTimePercentValue(ActivityRangeInfoCache range)
        {
            if (Utils.GetActiveActivityTime(range.Activity).TotalMilliseconds != 0)
            {
                TimeSpan elapsedTime = range.PauselessRange.Upper - range.PauselessRange.Lower;
                return elapsedTime.TotalSeconds / range.ActivityInfo.Time.TotalSeconds;
            }

            return 0;
        }

        public String GetTimePercent(ActivityRangeInfoCache range)
        {
            if (range.ActivityInfo.Time.TotalMilliseconds != 0)
            {
                return String.Format("{0:0.00} %", GetTimePercentValue(range) * 100.0);
            }

            return String.Empty;
        }

        public double GetDistancePercentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Max > 0)
            {
                double rangeDistance = range.PauselessRangeDistanceTrack.Max - range.PauselessRangeDistanceTrack.Min;

                return rangeDistance / range.ActivityPauselessDistanceTrack.Max;
            }

            return 0;
        }

        public String GetDistancePercent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Max > 0 && range.PauselessRangeDistanceTrack.Max != range.PauselessRangeDistanceTrack.Min)
            {
                return String.Format("{0:0.00} %", GetDistancePercentValue(range) * 100.0);
            }

            return String.Empty;
        }

        public double GetElevationChangeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return GetEndElevationValue(range) - GetStartElevationValue(range);
            }

            return 0;
        }

        public String GetElevationChange(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetElevationChangeValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(value), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetStartElevationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeElevationTrack[0].Value, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetStartElevation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetStartElevationValue(range) < 0)
                {
                    sign = "-";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(Math.Round(GetStartElevationValue(range), 1)), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetEndElevationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeElevationTrack[range.PauselessRangeElevationTrack.Count - 1].Value, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetEndElevation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetEndElevationValue(range) < 0)
                {
                    sign = "-";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(Math.Round(GetEndElevationValue(range), 1)), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetTotalAscentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);
                double totalAscent = 0;

                // Remove totals row
                categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low > 0)
                    {
                        totalAscent += zoneInfo.ElevationChangeMeters;
                    }
                }

                return Length.Convert(totalAscent, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetTotalAscent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalAscentValue(range), 1), Length.LabelAbbr(range.Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetTotalDescentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);
                double totalDescent = 0;

                // Remove totals row
                categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.High < 0)
                    {
                        totalDescent += zoneInfo.ElevationChangeMeters;
                    }
                }

                return Math.Abs(Length.Convert(totalDescent, Length.Units.Meter, range.Activity.Category.ElevationUnits));
            }

            return 0;
        }

        public String GetTotalDescent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalDescentValue(range), 1), Length.LabelAbbr(range.Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetAscentDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);
                double totalAscentDistance = 0;

                // Remove totals row
                categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low > 0)
                    {
                        totalAscentDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalAscentDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetAscentDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetAscentDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetFlatDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);
                double totalFlatDistance = 0;

                // Remove totals row
                categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low < 0 && zoneInfo.Zone.High > 0)
                    {
                        totalFlatDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalFlatDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetFlatDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetFlatDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetDescentDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);
                double totalDescentDistance = 0;

                // Remove totals row
                categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.High < 0)
                    {
                        totalDescentDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalDescentDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetDescentDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetDescentDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetMinPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                // Use max since pace is inverse of speed
                return Utils.SpeedToPace(Length.Convert(range.PauselessRangeSpeedTrack.Min, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
            }

            return 0;
        }

        public double GetMinSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Min, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetMinSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetMinPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMinSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetAvgPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                double distanceInUnit = GetDistanceValue(range);
                TimeSpan timeDuration = GetElapsedTimeValue(range);

                return timeDuration.TotalMinutes / distanceInUnit;
            }

            return 0;
        }

        public double GetAvgSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetAvgSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetAvgPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetMaxSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public double GetMaxPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                // Use minsince pace is inverse of speed
                return Utils.SpeedToPace(Length.Convert(range.PauselessRangeSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
            }

            return 0;
        }

        public String GetMaxSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetMaxPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMaxSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetPaceVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                double activityAvgPace = Utils.SpeedToPace(Length.Convert(range.ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);

                return GetAvgPaceValue(range) - activityAvgPace;
            }

            return 0;
        }

        public double GetSpeedVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                double speedVsAvg = range.PauselessRangeSpeedTrack.Avg - range.ActivityPauselessSpeedTrack.Avg;

                return Length.Convert(speedVsAvg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetSpeedVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                String sign = String.Empty;
                String valueText = String.Empty;
                double value = 0;

                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    value = GetPaceVsAvgValue(range);

                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetSpeedVsAvgValue(range);
                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public double GetVerticalSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return GetElevationChangeValue(range) / GetElapsedTimeValue(range).TotalHours;
            }

            return 0;
        }

        public String GetVerticalSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetVerticalSpeedValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetMinCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Min;
            }

            return 0;
        }

        public String GetMinCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMinCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetAvgCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Avg;
            }

            return 0;
        }

        public String GetAvgCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetMaxCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Max;
            }

            return 0;
        }
        public String GetMaxCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMaxCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetCadenceVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Avg - range.ActivityPauselessCadenceTrack.Avg;
            }

            return 0;
        }
        public String GetCadenceVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetCadenceVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetMinHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Min;
            }

            return 0;
        }

        public String GetMinHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetMinHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetMinHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", GetMinHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetAvgHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Avg;
            }

            return 0;
        }

        public String GetAvgHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetAvgHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetAvgHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0.0}{1}", GetAvgHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetMaxHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Max;
            }

            return 0;
        }

        public String GetMaxHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetMaxHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetMaxHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", GetMaxHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetHRVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Avg - range.ActivityPauselessHeartRateTrack.Avg;
            }

            return 0;
        }

        public String GetHRVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetHRVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetMinGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return range.PauselessRangeGradeTrack.Min * 100.0f;
            }

            return 0;
        }

        public String GetMinGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetMinGradeValue(range));
            }

            return String.Empty;
        }

        public double GetAvgGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                double elevationChangeValue = Length.Convert(GetElevationChangeValue(range), range.Activity.Category.ElevationUnits, range.Activity.Category.DistanceUnits);

                return elevationChangeValue / GetDistanceValue(range) * 100;
            }

            return 0;
        }

        public String GetAvgGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetAvgGradeValue(range));
            }

            return String.Empty;
        }

        public double GetAvgAscentGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0 && GetAscentDistanceValue(range) > 0)
            {
                double ascentMeters = Length.Convert(GetTotalAscentValue(range), range.Activity.Category.ElevationUnits, Length.Units.Meter);
                double ascentDistanceMeters = Length.Convert(GetAscentDistanceValue(range), range.Activity.Category.DistanceUnits, Length.Units.Meter);

                return (ascentMeters * 100) / ascentDistanceMeters;
            }

            return 0;
        }

        public String GetAvgAscentGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgAscentGradeValue(range), 1));
            }

            return String.Empty;
        }

        public double GetAvgDescentGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0 && GetDescentDistanceValue(range) > 0)
            {
                double descentMeters = Length.Convert(GetTotalDescentValue(range), range.Activity.Category.ElevationUnits, Length.Units.Meter);
                double descentDistanceMeters = Length.Convert(GetDescentDistanceValue(range), range.Activity.Category.DistanceUnits, Length.Units.Meter);

                return (descentMeters * 100) / descentDistanceMeters;
            }

            return 0;
        }

        public String GetAvgDescentGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgDescentGradeValue(range), 1));
            }

            return String.Empty;
        }

        public double GetMaxGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return range.PauselessRangeGradeTrack.Max * 100.0f;
            }

            return 0;
        }

        public String GetMaxGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetMaxGradeValue(range));
            }

            return String.Empty;
        }

        public double GetGradeVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return (range.PauselessRangeGradeTrack.Avg - range.ActivityPauselessGradeTrack.Avg) * 100.0f;
            }

            return 0;
        }

        public String GetGradeVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetGradeVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetMinPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Min;
            }

            return 0;
        }

        public String GetMinPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMinPowerValue(range));
            }

            return String.Empty;
        }

        public double GetAvgPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Avg;
            }

            return 0;
        }

        public String GetAvgPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgPowerValue(range));
            }

            return String.Empty;
        }

        public double GetMaxPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Max;
            }

            return 0;
        }

        public String GetMaxPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMaxPowerValue(range));
            }

            return String.Empty;
        }

        public double GetPowerVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Avg - range.ActivityPauselessPowerTrack.Avg;
            }

            return 0;
        }

        public String GetPowerVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetPowerVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

#endregion

#region All ranges stats

        public String GetStartTime(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndTime(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetStartDistance(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndDistance(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetElapsedTime(List<ActivityRangeInfoCache> allRanges)
        {
            TimeSpan elapsedTime = new TimeSpan(0, 0, 0);

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                elapsedTime += GetElapsedTimeValue(range);
            }

            return String.Format("{0:00}:{1:00}:{2:00}", elapsedTime.Hours, elapsedTime.Minutes, elapsedTime.Seconds);
        }

        public String GetDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.MovingDistanceMetersTrack.Count > 0)
            {
                double distance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    distance += GetDistanceValue(range);
                }

                return String.Format("{0:0.00} {1}", distance, Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetTimePercentValue(List<ActivityRangeInfoCache> allRanges)
        {
                double totalTimePercent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalTimePercent += GetTimePercentValue(range);
                }

                return totalTimePercent;
        }

        public String GetTimePercent(List<ActivityRangeInfoCache> allRanges)
        {
            if (GetTimePercentValue(allRanges) != 0)
            {
                return String.Format("{0:0.00} %", GetTimePercentValue(allRanges) * 100.0);
            }

            return String.Empty;
        }

        public double GetDistancePercentValue(List<ActivityRangeInfoCache> allRanges)
        {
            double totalDistancePercent = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                totalDistancePercent += GetDistancePercentValue(range);
            }

            return totalDistancePercent;
        }

        public String GetDistancePercent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.MovingDistanceMetersTrack.Max > 0)
            {
                return String.Format("{0:0.00} %", GetDistancePercentValue(allRanges) * 100.0);
            }

            return String.Empty;
        }

        public String GetStartElevation(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndElevation(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetElevationChange(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double elevationChange = 0;
                String sign = String.Empty;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    elevationChange += GetElevationChangeValue(range);
                }

                if (elevationChange < 0)
                {
                    sign = "-";
                }
                else if (elevationChange > 0)
                {
                    sign = "+";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(elevationChange), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }


        public double GetTotalAscentValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalAscent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalAscent += GetTotalAscentValue(range);
                }

                return totalAscent;
            }

            return 0;
        }

        public String GetTotalAscent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalAscentValue(allRanges), 1), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetTotalDescentValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDescent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDescent += GetTotalDescentValue(range);
                }

                return totalDescent;
            }

            return 0;
        }

        public String GetTotalDescent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalDescentValue(allRanges), 1), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetAscentDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetAscentDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetAscentDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetAscentDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetFlatDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetFlatDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetFlatDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetFlatDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetDescentDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetDescentDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetDescentDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetDescentDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public String GetMinSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace;
                double min = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                if (isPace)
                {
                    min = 0;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use max since pace is inverse of speed
                        min = Math.Max(min, GetMinPaceValue(range));
                    }
                    else
                    {
                        min = Math.Min(min, GetMinSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)min;
                    double secondValue = Math.Round((min - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", min);
                }
            }

            return String.Empty;
        }

        public double GetAvgPaceValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgPaceValue(range) * GetDistancePercentValue(range);
            }

            return avg / GetDistancePercentValue(allRanges);
        }

        public double GetAvgSpeedValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgSpeedValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                if (allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double avgPaceValue = GetAvgPaceValue(allRanges);
                    int minuteValue = (int)avgPaceValue;
                    double secondValue = Math.Round((avgPaceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(allRanges));
                }
            }

            return String.Empty;
        }

        public String GetMaxSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace;
                double max = 0;

                if (isPace)
                {
                    max = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use min since pace is inverse of speed
                        max = Math.Min(max, GetMaxPaceValue(range));
                    }
                    else
                    {
                        max = Math.Max(max, GetMaxSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)max;
                    double secondValue = Math.Round((max - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", max);
                }
            }

            return String.Empty;
        }

        public String GetSpeedVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                double value;
                String valueText;

                if (allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    value = GetAvgPaceValue(allRanges) - Utils.SpeedToPace(Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetAvgSpeedValue(allRanges) - Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                String sign = String.Empty;

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public double GetVerticalSpeedValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetVerticalSpeedValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetVerticalSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetVerticalSpeedValue(allRanges) < 0)
                {
                    sign = "-";
                }
                else if (GetVerticalSpeedValue(allRanges) > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(GetVerticalSpeedValue(allRanges)), sign);
            }

            return String.Empty;
        }

        public String GetMinCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessCadenceTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinCadenceValue(range));
                }

                return String.Format("{0:0.0}", min);
            }

            return String.Empty;
        }

        public double GetAvgCadenceValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgCadenceValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgCadenceValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMaxCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double max = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxCadenceValue(range));
                }

                return String.Format("{0:0.0}", max);
            }

            return String.Empty;
        }

        public String GetCadenceVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double cadenceVsAvg = GetAvgCadenceValue(allRanges) - allRanges[0].ActivityPauselessCadenceTrack.Avg;
                String sign = String.Empty;

                if (cadenceVsAvg < 0)
                {
                    sign = "-";
                }
                else if (cadenceVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(cadenceVsAvg), sign);
            }

            return String.Empty;
        }

        public String GetMinHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessHeartRateTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinHRValue(range));
                }

                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", min / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, min - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", min, percentMax);
            }

            return String.Empty;
        }

        public double GetAvgHRValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgHRValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double avg = GetAvgHRValue(allRanges);
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", avg / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, avg - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0.0}{1}", avg, percentMax);
            }

            return String.Empty;
        }

        public String GetMaxHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double max = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxHRValue(range));
                }

                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", max / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, max - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", max, percentMax);
            }

            return String.Empty;
        }

        public String GetHRVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double HRVsAvg = GetAvgHRValue(allRanges) - allRanges[0].ActivityPauselessHeartRateTrack.Avg;
                String sign = String.Empty;

                if (HRVsAvg < 0)
                {
                    sign = "-";
                }
                else if (HRVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(HRVsAvg), sign);
            }

            return String.Empty;
        }

        public String GetMinGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessGradeTrack.Max * 100;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinGradeValue(range));
                }

                return String.Format("{0:0.0}%", min);
            }

            return String.Empty;
        }

        public double GetAvgGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgGradeValue(range) * GetDistancePercentValue(range);
            }

            return avg / GetDistancePercentValue(allRanges);
        }

        public String GetAvgGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetAvgGradeValue(allRanges));
            }

            return String.Empty;
        }

        public double GetAvgAscentGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalAscent = 0;
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalAscent += GetTotalAscentValue(range);
                    totalDistance += GetAscentDistanceValue(range);
                }

                if (totalDistance > 0)
                {
                    return (totalAscent * 100) / Length.Convert(totalDistance, allRanges[0].Activity.Category.DistanceUnits, allRanges[0].Activity.Category.ElevationUnits);
                }
            }

            return 0;
        }

        public String GetAvgAscentGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgAscentGradeValue(allRanges), 1));
            }

            return String.Empty;
        }

        public double GetAvgDescentGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDescent = 0;
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDescent += GetTotalDescentValue(range);
                    totalDistance += GetDescentDistanceValue(range);
                }

                if (totalDistance > 0)
                {
                    return (totalDescent * 100) / Length.Convert(totalDistance, allRanges[0].Activity.Category.DistanceUnits, allRanges[0].Activity.Category.ElevationUnits);
                }
            }

            return 0;
        }

        public String GetAvgDescentGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgDescentGradeValue(allRanges), 1));
            }

            return String.Empty;
        }

        public String GetMaxGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double max = allRanges[0].ActivityPauselessGradeTrack.Min * 100.0f;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxGradeValue(range));
                }

                return String.Format("{0:0.0}%", max);
            }

            return String.Empty;
        }

        public String GetGradeVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double gradeVsAvg = GetAvgGradeValue(allRanges) - (allRanges[0].ActivityPauselessGradeTrack.Avg * 100.0f);
                String sign = String.Empty;

                if (gradeVsAvg < 0)
                {
                    sign = "-";
                }
                else if (gradeVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(gradeVsAvg), sign);
            }

            return String.Empty;
        }

        public String GetMinPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessPowerTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinPowerValue(range));
                }

                return String.Format("{0:0.0}", min);
            }

            return String.Empty;
        }

        public double GetAvgPowerValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgPowerValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgPowerValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMaxPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double max = allRanges[0].ActivityPauselessPowerTrack.Min;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxPowerValue(range));
                }

                return String.Format("{0:0.0}", max);
            }

            return String.Empty;
        }

        public String GetPowerVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double powerVsAvg = GetAvgPowerValue(allRanges) - allRanges[0].ActivityPauselessPowerTrack.Avg;
                String sign = String.Empty;

                if (powerVsAvg < 0)
                {
                    sign = "-";
                }
                else if (powerVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(powerVsAvg), sign);
            }

            return String.Empty;
        }

#endregion


        private String GetValueMemberFromGuid(Guid id)
        {
            Dictionary<Guid, String> valueMemberToGuidTable = new Dictionary<Guid, String>();

            valueMemberToGuidTable.Add(new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"), "StartTime");
            valueMemberToGuidTable.Add(new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"), "EndTime");
            valueMemberToGuidTable.Add(new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"), "ElapsedTime");
            valueMemberToGuidTable.Add(new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"), "StartDistance");
            valueMemberToGuidTable.Add(new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"), "EndDistance");
            valueMemberToGuidTable.Add(new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"), "Distance");
            valueMemberToGuidTable.Add(new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"), "TimePercent");
            valueMemberToGuidTable.Add(new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"), "DistancePercent");
            valueMemberToGuidTable.Add(new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"), "StartElevation");
            valueMemberToGuidTable.Add(new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"), "EndElevation");
            valueMemberToGuidTable.Add(new Guid("1b06055e-c232-4383-9d43-50e558a69af1"), "ElevationChange");
            valueMemberToGuidTable.Add(new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"), "TotalAscent");
            valueMemberToGuidTable.Add(new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"), "TotalDescent");
            valueMemberToGuidTable.Add(new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"), "AscentDistance");
            valueMemberToGuidTable.Add(new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"), "FlatDistance");
            valueMemberToGuidTable.Add(new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"), "DescentDistance");
            valueMemberToGuidTable.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"), "MinSpeed");
            valueMemberToGuidTable.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"), "AvgSpeed");
            valueMemberToGuidTable.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"), "MaxSpeed");
            valueMemberToGuidTable.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"), "SpeedVsAvg");
            valueMemberToGuidTable.Add(new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"), "VerticalSpeed");
            valueMemberToGuidTable.Add(new Guid("72a37849-3d55-457b-8cc1-a87206104261"), "MinCadence");
            valueMemberToGuidTable.Add(new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"), "AvgCadence");
            valueMemberToGuidTable.Add(new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"), "MaxCadence");
            valueMemberToGuidTable.Add(new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"), "CadenceVsAvg");
            valueMemberToGuidTable.Add(new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"), "MinHR");
            valueMemberToGuidTable.Add(new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"), "AvgHR");
            valueMemberToGuidTable.Add(new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"), "MaxHR");
            valueMemberToGuidTable.Add(new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"), "HRVsAvg");
            valueMemberToGuidTable.Add(new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"), "MinPower");
            valueMemberToGuidTable.Add(new Guid("550e262d-2563-4131-9f37-04ceda1a8097"), "AvgPower");
            valueMemberToGuidTable.Add(new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"), "MaxPower");
            valueMemberToGuidTable.Add(new Guid("76695911-807c-44cc-ac7a-3367184db00e"), "PowerVsAvg");
            valueMemberToGuidTable.Add(new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"), "MinGrade");
            valueMemberToGuidTable.Add(new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"), "AvgGrade");
            valueMemberToGuidTable.Add(new Guid("3210bbff-605b-4978-abca-4f336a2268b0"), "AvgAscentGrade");
            valueMemberToGuidTable.Add(new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"), "AvgDescentGrade");
            valueMemberToGuidTable.Add(new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"), "MaxGrade");
            valueMemberToGuidTable.Add(new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"), "GradeVsAvg");

            Debug.Assert(valueMemberToGuidTable.ContainsKey(id));

            return valueMemberToGuidTable[id];
        }

        public Guid GetGuidFromValueMember(String valueMember)
        {
            Dictionary<String, Guid> GuidToValueMemberTable = new Dictionary<String, Guid>();

            GuidToValueMemberTable.Add("StartTime", new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"));
            GuidToValueMemberTable.Add("EndTime", new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"));
            GuidToValueMemberTable.Add("ElapsedTime", new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"));
            GuidToValueMemberTable.Add("StartDistance", new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"));
            GuidToValueMemberTable.Add("EndDistance", new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"));
            GuidToValueMemberTable.Add("Distance", new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"));
            GuidToValueMemberTable.Add("TimePercent", new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"));
            GuidToValueMemberTable.Add("DistancePercent", new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"));
            GuidToValueMemberTable.Add("StartElevation", new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"));
            GuidToValueMemberTable.Add("EndElevation", new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"));
            GuidToValueMemberTable.Add("ElevationChange", new Guid("1b06055e-c232-4383-9d43-50e558a69af1"));
            GuidToValueMemberTable.Add("TotalAscent", new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"));
            GuidToValueMemberTable.Add("TotalDescent", new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"));
            GuidToValueMemberTable.Add("AscentDistance", new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"));
            GuidToValueMemberTable.Add("FlatDistance", new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"));
            GuidToValueMemberTable.Add("DescentDistance", new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"));
            GuidToValueMemberTable.Add("MinSpeed", new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"));
            GuidToValueMemberTable.Add("AvgSpeed", new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"));
            GuidToValueMemberTable.Add("MaxSpeed", new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"));
            GuidToValueMemberTable.Add("SpeedVsAvg", new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"));
            GuidToValueMemberTable.Add("VerticalSpeed", new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"));
            GuidToValueMemberTable.Add("MinCadence", new Guid("72a37849-3d55-457b-8cc1-a87206104261"));
            GuidToValueMemberTable.Add("AvgCadence", new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"));
            GuidToValueMemberTable.Add("MaxCadence", new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"));
            GuidToValueMemberTable.Add("CadenceVsAvg", new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"));
            GuidToValueMemberTable.Add("MinHR", new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"));
            GuidToValueMemberTable.Add("AvgHR", new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"));
            GuidToValueMemberTable.Add("MaxHR", new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"));
            GuidToValueMemberTable.Add("HRVsAvg", new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"));
            GuidToValueMemberTable.Add("MinPower", new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"));
            GuidToValueMemberTable.Add("AvgPower", new Guid("550e262d-2563-4131-9f37-04ceda1a8097"));
            GuidToValueMemberTable.Add("MaxPower", new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"));
            GuidToValueMemberTable.Add("PowerVsAvg", new Guid("76695911-807c-44cc-ac7a-3367184db00e"));
            GuidToValueMemberTable.Add("MinGrade", new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"));
            GuidToValueMemberTable.Add("AvgGrade", new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"));
            GuidToValueMemberTable.Add("AvgAscentGrade", new Guid("3210bbff-605b-4978-abca-4f336a2268b0"));
            GuidToValueMemberTable.Add("AvgDescentGrade", new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"));
            GuidToValueMemberTable.Add("MaxGrade", new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"));
            GuidToValueMemberTable.Add("GradeVsAvg", new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"));

            Debug.Assert(GuidToValueMemberTable.ContainsKey(valueMember));

            return GuidToValueMemberTable[valueMember];
        }

        private static FilteredStatisticsStatisticsProvider m_Instance = null;
    }
}